package com.chengziting.cloud.zk;

import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;

import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultTreeModel;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class ZKTool extends JFrame implements ActionListener, ItemListener , TreeSelectionListener {
    private JLabel lbConnectionStr = new JLabel("ConnectionString:");
    private JTextField tfConnectionStr = new JTextField("127.0.0.1:12181");
    private JButton btnConnect = new JButton("Connect");
    private JButton btnRefresh = new JButton("Refresh");
    private JComboBox comboOption = new JComboBox(new String[]{"Read","Write","Update","Delete"});
    private JLabel lbOption = new JLabel("Option:");
    private JLabel lbPath = new JLabel("Path:");
    private JTextField tfPath = new JTextField(20);
    private JLabel lbData = new JLabel("Data:");
    private JTextArea tfData = new JTextArea();
    private JButton btnExecute = new JButton("Read");
    private JLabel lbMessage = new JLabel("message:");

    private ZKTreeNode treeRootNode ;
    private JTree  treeView ;

    private ZooKeeper zk = null;
    private Watcher watcher = new ZKWatcher();

    public ZKTool(){

        treeRootNode = new ZKTreeNode("/","/");
        treeView = new JTree(treeRootNode);
        treeView.setExpandsSelectedPaths(true);
        treeView.setShowsRootHandles(true);
        treeView.setCellRenderer(new ZKTreeNode.ZKTreeCellRenderer());
        treeView.addTreeSelectionListener(this);

        initUI();
        registerEvent();
        this.setSize(500,500);
        this.setTitle("ZKTool");
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setVisible(true);

        btnRefresh.setEnabled(false);
    }



    private void initUI(){
        Image icon = null;
        try {
            icon = ImageIO.read(getClass().getClassLoader().getResource("icon/zookeeper.gif"));
        } catch (IOException e) {
            e.printStackTrace();
        }
        this.setIconImage(icon);

        JPanel topPane = getTopPane();
        Component centerPane = getCenterPane();
        JPanel bottomPane = getBottomPane();


        this.setLayout(new BorderLayout());
        this.add(topPane,BorderLayout.NORTH);
        this.add(centerPane,BorderLayout.CENTER);
        this.add(bottomPane,BorderLayout.SOUTH);

        onConnectedChanged(false);

        tfData.setWrapStyleWord(false);
        tfData.setLineWrap(true);
    }

    private void registerEvent(){
        btnConnect.addActionListener(this);
        btnRefresh.addActionListener(this);
        btnExecute.addActionListener(this);
        comboOption.addItemListener(this);
        Runtime.getRuntime().addShutdownHook(new Thread(){
            @Override
            public void run() {
                if(zk.getState() == ZooKeeper.States.CONNECTED){
                    try {
                        zk.close();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    }

    private JPanel getTopPane(){
        GridBagLayout layout = new GridBagLayout();
        GridBagConstraints c = new GridBagConstraints();
        JPanel p = new JPanel(layout);
        p.add(lbConnectionStr,c);
        c = new GridBagConstraints();
        c.gridx = 1;
        c.weightx = 1;
        c.fill = GridBagConstraints.HORIZONTAL;
        p.add(tfConnectionStr,c);
        c = new GridBagConstraints();
        c.gridx = 2;
        c.gridwidth = 1;
        p.add(btnConnect,c);
        c = new GridBagConstraints();
        c.gridx = 3;
        c.gridwidth = 1;
        p.add(btnRefresh,c);

        return p;
    }

    private JPanel getCenterLeftPane(){
        JPanel p = new JPanel(new BorderLayout());
        JScrollPane sp = new JScrollPane(treeView);
        p.add(sp,BorderLayout.CENTER);
        return p;
    }
    private JPanel getCenterRightPane(){
        GridBagLayout layout = new GridBagLayout();
        GridBagConstraints c = new GridBagConstraints();
        Insets insets = new Insets(5,5,5,5);
        JPanel p = new JPanel(layout);
        p.add(lbPath,c);
        c.gridx = 1;
        c.weightx = 1;
        c.fill = GridBagConstraints.HORIZONTAL;
        c.insets = insets;
        p.add(tfPath,c);
        c = new GridBagConstraints();
        c.gridx = 0;
        c.gridy = 1;
        c.insets = insets;
        p.add(lbData,c);
        c = new GridBagConstraints();
        c.gridx = 1;
        c.gridy = 1;
        c.fill = GridBagConstraints.HORIZONTAL;
        c.insets = insets;
        p.add(tfData,c);
        c = new GridBagConstraints();
        c.gridy = 2;
        c.insets = insets;
        p.add(lbOption,c);
        c = new GridBagConstraints();
        c.gridy = 2;
        c.gridx = 1;
        c.fill = GridBagConstraints.HORIZONTAL;
        c.insets = insets;
        p.add(comboOption,c);
        c = new GridBagConstraints();
        c.gridy = 3;
        c.gridwidth = 2;
        c.fill = GridBagConstraints.HORIZONTAL;
        c.weightx = 1;
        c.insets = insets;
        p.add(btnExecute,c);

        return p;
    }

    private Component getCenterPane(){
        JPanel leftPane = getCenterLeftPane();
        JPanel rightPane = getCenterRightPane();
//        GridBagLayout layout = new GridBagLayout();
//        JPanel p = new JPanel(layout);
//        GridBagConstraints c = new GridBagConstraints();
//        c.fill = GridBagConstraints.BOTH;
//        c.weightx = 1;
//        c.weighty = 1;
//        p.add(leftPane,c);
//        c = new GridBagConstraints();
//        c.fill = GridBagConstraints.HORIZONTAL;
//        c.anchor = GridBagConstraints.NORTH;
//        c.weighty = 1;
//        c.weightx = 1;
//        c.gridx = 1;
//        p.add(rightPane,c);
        JPanel p = new JPanel(new BorderLayout(3,3));
        p.add(rightPane,BorderLayout.NORTH);
        JSplitPane sp = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,leftPane,p);
        sp.setDividerLocation(200);

        return sp;
    }
    private JPanel getBottomPane(){
        JPanel p = new JPanel(new BorderLayout());
        p.add(lbMessage,BorderLayout.CENTER);
        return p;
    }

    public static void main(String []args){
        new ZKTool();
    }

    public void actionPerformed(ActionEvent e) {
        if(e.getSource() == btnConnect){
            String connStr = tfConnectionStr.getText();
            if("".equals(connStr)){
                JOptionPane.showMessageDialog(this,"Connection string not empty!");
                return;
            }
            if(doConnect(connStr)){
                onConnectedChanged(true);
            }
        }else if(e.getSource() == btnRefresh){
            doReloadPaths();
        }else if(e.getSource() == btnExecute){
            String option = comboOption.getSelectedItem().toString();
            if("Read".equals(option)){
                doRead(tfPath.getText());
            }else if("Write".equals(option)){
                doWrite(tfPath.getText(),tfData.getText());
            }else if ("Delete".equals(option)){
                doDelete(tfPath.getText());
            }else if("Update".equals(option)){
                doUpdate(tfPath.getText(),tfData.getText());
            }
        }
    }

    public void itemStateChanged(ItemEvent e) {
        btnExecute.setText(e.getItem().toString());
    }

    private boolean doConnect(String connStr){
        int tryTimes = 10;
        try {
            zk = new ZooKeeper(connStr,10000,watcher);
            while (zk.getState() == ZooKeeper.States.CONNECTING && (tryTimes >0)){
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                tryTimes--;
            }

            boolean connected = zk.getState() == ZooKeeper.States.CONNECTED;
            if(!connected){
                try {
                    zk.close();
                    lbMessage.setText("connect fail.");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return connected;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }

    private void doLoadPaths(String path,ZKTreeNode node) throws KeeperException, InterruptedException {
        checkState();
        List<String> children = zk.getChildren(path,false);
        children.forEach(m->{
            try {
                String tmpPath;
                if(path.equals("/")) {
                    tmpPath =path + m;
                }else{
                    tmpPath = path+"/"+m;
                }

                String data = new String(zk.getData(tmpPath,false,null));
                ZKTreeNode tmpnode = new ZKTreeNode(tmpPath,data);
                node.add(tmpnode);
                doLoadPaths(tmpPath,tmpnode);
            } catch (KeeperException e) {
                e.printStackTrace();
                onError(e);
            } catch (InterruptedException e) {
                e.printStackTrace();
                onError(e);
            }
        });
        treeView.repaint();
    }

    private void doReloadPaths(){
        treeRootNode.removeAllChildren();

        try {
            doLoadPaths("/",treeRootNode);
            TreeModelEvent tme = new TreeModelEvent(treeView,treeRootNode.getPath());
            ((DefaultTreeModel)treeView.getModel()).reload();
            treeView.expandRow(0);
        } catch (KeeperException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    private void checkState(){
        if(!(ZooKeeper.States.CONNECTED == zk.getState())){
            try {
                zk.close();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            onConnectedChanged(doConnect(this.tfConnectionStr.getText()));
        }
    }

    private void doRead(String path) {
        try {
            byte []bytes = zk.getData(path,false,null);
            String data = new String(bytes);
            lbMessage.setText(data);
            tfData.setText(data);
        } catch (KeeperException e) {
            e.printStackTrace();
            onError(e);
        } catch (InterruptedException e) {
            e.printStackTrace();
            onError(e);
        }
    }
    private void doWrite(String path,String data){
        try {
            String result = zk.create(path, data.getBytes(),ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);

            lbMessage.setText(String.format("Create path [%s] success.",path));
            doReloadPaths();
        } catch (KeeperException e) {
            e.printStackTrace();
            onError(e);
        } catch (InterruptedException e) {
            e.printStackTrace();
            onError(e);
        }
    }
    private void doDelete(String path){
        try {
            Stat stat = zk.exists(path,false);
            if(stat == null){
                return;
            }else{
                List<String> children = zk.getChildren(path,false);
                if(children.size() > 0){
                    String msg = String.format("Cannot delete %s, this path has %d child:%s.",path,children.size(),children.toString());
                    lbMessage.setText(msg);
                    return;
                }
            }
            zk.delete(path,0);
            lbMessage.setText(String.format("Create path [%s] success.",path));
            doReloadPaths();
        } catch (InterruptedException e) {
            e.printStackTrace();
            onError(e);
        } catch (KeeperException e) {
            e.printStackTrace();
            onError(e);
        }
    }

    private void doUpdate(String path,String data){
        try {
            Stat stat = zk.exists(path,false);
            if(stat == null){
                doWrite(path,data);
            }else{
                zk.setData(path,data.getBytes(),0);
            }
            doReloadPaths();
        } catch (KeeperException e) {
            e.printStackTrace();
            onError(e);
        } catch (InterruptedException e) {
            e.printStackTrace();
            onError(e);
        }
    }


    private void onError(Exception e){
        lbMessage.setText(e.getLocalizedMessage());
        if(e instanceof KeeperException.SessionExpiredException){
            onConnectedChanged(false);
        }
    }

    private void onConnectedChanged(boolean connected){
        btnConnect.setEnabled(!connected);
        tfConnectionStr.setEditable(!connected);
        btnExecute.setEnabled(connected);
        treeView.setEnabled(connected);
        btnRefresh.setEnabled(connected);
        if(connected){

            ExecutorService executorService =Executors.newCachedThreadPool();
            Future future = executorService.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        doLoadPaths("/",treeRootNode);
                    } catch (KeeperException e) {
                        e.printStackTrace();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        else{
            treeRootNode.removeAllChildren();
        }
    }

    @Override
    public void valueChanged(TreeSelectionEvent e) {
        if(treeView.getLastSelectedPathComponent() instanceof ZKTreeNode){
            ZKTreeNode tn = (ZKTreeNode)treeView.getLastSelectedPathComponent();
            tfPath.setText(tn.getKey());
            tfData.setText(tn.getData());
        }
    }

    public class ZKWatcher implements Watcher{

        public void process(WatchedEvent watchedEvent) {
            if(watchedEvent.getType() == Event.EventType.NodeCreated || watchedEvent.getType() == Event.EventType.NodeDeleted){
                doReloadPaths();
            }
        }
    }

}
